﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;

namespace WebCacheHelper
{
	/// <summary>
	/// A strongly typed wrapper for working with System.Web.Caching.Cache.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class Cache<T>
	{
		private string Name;
		private TimeSpan SlidingExpiration = System.Web.Caching.Cache.NoSlidingExpiration;
		private DateTime AbsoluteExpiration = System.Web.Caching.Cache.NoAbsoluteExpiration;
		private TimeSpan IntervalExpiration;
		private bool IsIntervalBased = false;
		private Func<T> ValueFactory = null;
		private System.Threading.Timer IntervalTimer = null;

		/// <summary>
		/// Creates a new Cache object with a GUID for a name.
		/// </summary>
		public Cache()
		{
			Name = Guid.NewGuid().ToString();
		}

		/// <summary>
		/// Creates a new Cache object with a GUID for a name as well as the specified intervalExpiration and valueFactory.
		/// </summary>
		/// <param name="intervalExpiration">The interval at which the object should be set by the valueFactory.</param>
		/// <param name="valueFactory">The value that this Func returns will be used to set the value in the cache.</param>
		public Cache(TimeSpan intervalExpiration, Func<T> valueFactory)
		{
			Name = Guid.NewGuid().ToString();
			ValueFactory = valueFactory;
			IsIntervalBased = true;
			IntervalExpiration = intervalExpiration;
		}

		/// <summary>
		/// Creates a new Cache object with a GUID for a name and the specified slidingExpiration.
		/// </summary>
		/// <param name="slidingExpiration">The time between when the cache was last accessed and when it expires.</param>
		public Cache(TimeSpan slidingExpiration)
		{
			Name = Guid.NewGuid().ToString();
			SlidingExpiration = slidingExpiration;
		}

		/// <summary>
		/// Creates a new Cache object with a GUID for a name and the specified absoluteExpiration.
		/// </summary>
		/// <param name="absoluteExpiration">The time at which the cache should expire.</param>
		public Cache(DateTime absoluteExpiration)
		{
			Name = Guid.NewGuid().ToString();
			AbsoluteExpiration = absoluteExpiration;
		}

		/// <summary>
		/// Creates a new Cache object with the specified name;
		/// </summary>
		/// <param name="name">The name of the cache.  This must be unique.</param>
		public Cache(string name)
		{
			Name = name;
		}

		/// <summary>
		/// Creates a new Cache object with the specified name, intervalExpiration, and valueFactory.
		/// </summary>
		/// <param name="name">The name of the cache.  This must be unique.</param>
		/// <param name="intervalExpiration">The interval at which the object should be set by the valueFactory.</param>
		/// <param name="valueFactory">The value that this Func returns will be used to set the value in the cache.</param>
		public Cache(string name, TimeSpan intervalExpiration, Func<T> valueFactory = null)
		{
			Name = name;
			ValueFactory = valueFactory;
			IsIntervalBased = true;
			IntervalExpiration = intervalExpiration;
		}

		/// <summary>
		/// Creates a new Cache object with the specified name and slidingExpiration.
		/// </summary>
		/// <param name="name">The name of the cache.  This must be unique.</param>
		/// <param name="slidingExpiration">The time between when the cache was last accessed and when it expires.</param>
		public Cache(string name, TimeSpan slidingExpiration)
		{
			Name = name;
			SlidingExpiration = slidingExpiration;
		}

		/// <summary>
		/// Creates a new Cache object with the specified name and absoluteExpiration.
		/// </summary>
		/// <param name="name">The name of the cache.  This must be unique.</param>
		/// <param name="absoluteExpiration">The time at which the cache should expire.</param>
		public Cache(string name, DateTime absoluteExpiration)
		{
			Name = name;
			AbsoluteExpiration = absoluteExpiration;
		}

		private void SetNewValue(object stateInfo)
		{
			Value = ValueFactory();
		}

		/// <summary>
		/// Get or Set the value for the Cache.  If an intervalExpiration and valueFactory were specified in the constructor, then we will call the valueFactory on the first Get and then repeatedly at the specified interval.
		/// </summary>
		public T Value
		{
			get
			{
				if (IsIntervalBased && IntervalTimer == null && ValueFactory != null)
				{
					T val = ValueFactory();
					Value = val;

					var tc = new System.Threading.TimerCallback(SetNewValue);
					IntervalTimer = new System.Threading.Timer(tc, null, IntervalExpiration, IntervalExpiration);

					return val;
				}

				object o = HttpRuntime.Cache[Name];
				if (o is T)
				{
					return (T)o;
				}
				else
				{
					return default(T);
				}
			}
			set
			{
				if (IsIntervalBased)
				{
					HttpRuntime.Cache.Insert(Name, value, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
				}
				else
				{
					HttpRuntime.Cache.Insert(Name, value, null, AbsoluteExpiration, SlidingExpiration, CacheItemPriority.Default, null);
				}
			}
		}		

	}
}
